"""
Handling of arguments: options, arguments, file(s) content iterator

For small scripts that:
- read some command line options
- read some command line positional arguments
- iterate over all lines of some files given on the command line, or stdin if none given
- give usage message if positional arguments are missing
- give usage message if input files are missing and stdin is not redirected
"""

__author__ = 'Peter Kleiweg'
__version__ = '0.2'
__date__ = '2004/08/28'

import os, sys, getopt

class Args:
	"""
	Perform common tasks on command line arguments
	
	Instance data:
	progname (string) -- name of program
	opt (dictionary) -- options with values
	infile (string) -- name of current file being processed
	lineno (int) -- line number of last line read in current file
	linesum (int) -- total of lines read
	"""

	def __init__(self, usage='Usage: %(progname)s [opt...] [file...]'):
		"init, usage string: embed program name as %(progname)s"
		self.progname = os.path.basename(sys.argv[0])
		self.opt = {}
		self.infile = None
		self.lineno = 0
		self.linesum = 0
		self._argv = sys.argv[1:]
		self._usage = usage

	def __iter__(self):
		"iterator: set-up"
		if self._argv:
			self.infile = self._argv.pop(0)
			self._in = open(self.infile, 'r')
			self._stdin = False
		else:
			if sys.stdin.isatty():
				print "### USAGE in __iter__"
				#self.usage()  # Doesn't return
				return None
			self.infile = '<stdin>'
			self._in = sys.stdin
			self._stdin = True
		return self

	def next(self):
		"iterator: get next line, possibly from next file"
		while True:
			line = self._in.readline()
			if line:
				self.lineno += 1
				self.linesum += 1
				return line

			if self._stdin:
				break

			self._in.close()
			try:
				self.infile = self._argv.pop(0)
			except IndexError:
				break
			self.lineno = 0
			self._in = open(self.infile, 'r')

		self.lineno = -1
		self.infile = None
		raise StopIteration

	def getopt(self, shortopts, longopts=[]):
		"get options and merge into dict 'opt'"
		try:
			options, self._argv = getopt.getopt(self._argv, shortopts, longopts)
		except getopt.GetoptError:
			print "### USAGE in getopt"
			#self.usage()
			return None
		self.opt.update(dict(options))

	def shift(self):
		"pop first of remaining arguments (shift)"
		try:
			return self._argv.pop(0)
		except IndexError:
			#print "### USAGE in shift"
			#self.usage()
			return None


	def pop(self):
		"pop last of remaining arguments"
		try:
			return self._argv.pop()
		except IndexError:
			print "### USAGE in pop"
			#self.usage()
			return None

	def warning(self, text):
		"print warning message to stderr, possibly with filename and lineno"
		if self.lineno > 0:
			print >> sys.stderr, '%s:%i: warning: %s' % (self.infile, self.lineno, text)
		else:
			print >> sys.stderr, '\nWarning %s: %s\n' % (self.progname, text)

	def error(self, text):
		"print error message to stderr, possibly with filename and lineno, and exit"
		if self.lineno > 0:
			print >> sys.stderr, '%s:%i: %s' % (self.infile, self.lineno, text)
		else:
			print >> sys.stderr, '\nError %s: %s\n' % (self.progname, text)
		sys.exit(1)

	def usage(self):
		"print usage message, and exit"
		print >> sys.stderr
		print >> sys.stderr, self._usage % {'progname': self.progname}
		print >> sys.stderr        
		#sys.exit(1)


if __name__ == '__main__':

	a = Args('Usage: %(progname)s [-a value] [-b value] [-c] word [file...]')

	a.opt['-a'] = 'option a'    # set some default option values
	a.opt['-b'] = 'option b'    #
	a.getopt('a:b:c')           # get user supplied option values

	word = a.shift()            # get the first of the remaining arguments
								# use a.pop() to get the last instead

	for line in a:              # iterate over the contents of all remaining arguments (file names)
		if a.lineno == 1:
			print 'starting new file:', a.infile
		a.warning(line.rstrip())

	print 'Options:', a.opt
	print 'Word:', word
	print 'Total number of lines:', a.linesum

	print 'Command line:', sys.argv     # unchanged

	a.warning('warn 1')         # print a warning
	a.error('error')            # print an error message and exit
	a.warning('warn 2')         # this won't show

